package com.lyf.scm.plan.domain.service.impl;

import com.github.pagehelper.PageInfo;
import com.lyf.scm.common.constants.CommonConstants;
import com.lyf.scm.common.util.MD5Util;
import com.lyf.scm.plan.api.dto.*;
import com.lyf.scm.plan.domain.repository.SalesPlanEsLazyLoadingRepository;
import com.lyf.scm.plan.domain.service.CommonService;
import com.lyf.scm.plan.domain.service.IDistributedLockService;
import com.lyf.scm.plan.domain.service.SalesPlanEsLazyLoadingService;
import com.rome.arch.core.exception.RomeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static com.lyf.scm.common.constants.CommonConstants.*;

/**
 * es懒加载查询解决方案
 *
 * @author wangchlei
 * @date 2020/11/14
 */
@Slf4j
@Service
public class SalesPlanEsLazyLoadingServiceImpl implements SalesPlanEsLazyLoadingService {
    @Resource
    private SalesPlanEsLazyLoadingRepository salesPlanEsLazyLoadingRepository;
    @Resource
    private IDistributedLockService iDistributedLockService;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private CommonService commonService;

    @Override
    public PageInfo<EsReturnDTO> pageQuery(EsAggregationDTO esAggregation) {
        // 获取请求key
        String queryKey = getQueryKey(esAggregation);
        // 获取dataList分页下标
        int startIndex = (esAggregation.getPageNum() - 1) * esAggregation.getPageSize();
        int endIndex = esAggregation.getPageNum() * esAggregation.getPageSize() - 1;
        // 获取人员查询缓存数据
        ValueOperations<String, MemQueryCacheDTO> valueOperations = redisTemplate.opsForValue();
        MemQueryCacheDTO memQueryCacheDTO = valueOperations.get(CommonConstants.REDIS_CACHE_PREFIX_MEM_QUERY + queryKey);
        //如果没有该查询缓存，初始化缓存信息
        if (memQueryCacheDTO == null) {
            this.clearCache(CommonConstants.REDIS_CACHE_PREFIX_MEM_QUERY + queryKey, CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey);
            //没有缓存为首页信息
            return this.initalCache(queryKey, esAggregation, startIndex, endIndex);
        }
        //如果一开始就有缓存信息
        if (memQueryCacheDTO != null) {
            PageInfo<EsReturnDTO> pageInfo = new PageInfo();
            pageInfo.setPageNum(esAggregation.getPageNum());
            pageInfo.setHasNextPage(true);
            //获取数据集合
            List<EsReturnDTO> list = redisTemplate.opsForList().range(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, startIndex, endIndex);
            Long exsitSize = redisTemplate.opsForList().size(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey);
            int avaliableSize = list.size();
            //如果有数据，判断是否满足页面数据大小
            if (avaliableSize < (endIndex - startIndex + 1)) {
                //如果有数据可取
                if (CommonConstants.LOAD_STATUS_WAITING.equals(memQueryCacheDTO.getLoadStatus())) {
                    List<EsReturnDTO> dataList = this.calculationResult(esAggregation, startIndex, endIndex, memQueryCacheDTO, list);
                    redisTemplate.opsForValue().set(CommonConstants.REDIS_CACHE_PREFIX_MEM_QUERY + queryKey, memQueryCacheDTO, 30, TimeUnit.MINUTES);
                    if (CollectionUtils.isEmpty(dataList)) {
                        pageInfo.setHasNextPage(memQueryCacheDTO.getAfterKey() != null);
                        //更新缓存
                    } else {
                        int maxSize = endIndex - startIndex + 1 - avaliableSize;
                        int tmpIndex = dataList.size() - 1;
                        for (int i = 0; i < maxSize; i++) {
                            if (i <= tmpIndex) {
                                list.add(dataList.get(i));
                            }
                        }
                        //总数据量和end比较
                        if (dataList.size() + exsitSize > endIndex + 1) {
                            pageInfo.setHasNextPage(true);
                        } else {
                            pageInfo.setHasNextPage(memQueryCacheDTO.getAfterKey() != null);
                        }
                        redisTemplate.opsForList().rightPushAll(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, dataList);
                    }
                    redisTemplate.expire(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, 40, TimeUnit.MINUTES);
                } else {
                    pageInfo.setHasNextPage(false);
                    this.resetCache(queryKey);
                }
            } else {
                if (exsitSize > endIndex + 1) {
                    pageInfo.setHasNextPage(true);
                } else {
                    pageInfo.setHasNextPage(memQueryCacheDTO.getAfterKey() != null);
                }
                this.resetCache(queryKey);
            }
            //转换单位
            List<EsReturnDTO> esReturnList = salesPlanEsLazyLoadingRepository.conversionUnit(list, esAggregation.getUnitType());
            pageInfo.setList(esReturnList);
            commonService.reSetCacheRecord(esAggregation.getTableCode());
            return pageInfo;
        }
        return null;
    }

    /**
     * 从缓存中获取导出数据
     *
     * @param esAggregationDTO
     * @return {@link List<EsReturnDTO>}
     **/
    @Override
    public List<EsReturnDTO> queryFromCache(EsAggregationDTO esAggregationDTO) {
        // 获取请求key
        String queryKey = getQueryKey(esAggregationDTO);
        // 获取dataList分页下标
        int startIndex = (esAggregationDTO.getPageNum() - 1) * esAggregationDTO.getPageSize();
        int endIndex = esAggregationDTO.getPageNum() * esAggregationDTO.getPageSize() - 1;
        //获取缓存中的数据
        List<EsReturnDTO> list = redisTemplate.opsForList().range(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, startIndex, endIndex);
        if (list == null) {
            throw new RomeException("999", "请刷新页面重试");
        }
        //转换单位
        return salesPlanEsLazyLoadingRepository.conversionUnit(list, esAggregationDTO.getUnitType());
    }

    private void resetCache(String queryKey) {
        redisTemplate.expire(CommonConstants.REDIS_CACHE_PREFIX_MEM_QUERY + queryKey, 30, TimeUnit.MINUTES);
        redisTemplate.expire(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, 40, TimeUnit.MINUTES);
    }

    private void clearCache(String... keys) {
        for (String key : keys) {
            if (exists(key)) {
                redisTemplate.delete(key);
            }
        }
    }

    public boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }

    private PageInfo<EsReturnDTO> initalCache(String queryKey, EsAggregationDTO esAggregation, int startIndex, int endIndex) {
        PageInfo<EsReturnDTO> pageInfo = new PageInfo();
        pageInfo.setHasNextPage(true);
        pageInfo.setPageNum(esAggregation.getPageNum());
        //初始已取得的元素大小
        log.warn("该查询方案未命中缓存：{}", queryKey);
        //es以skuCode分组，求出该条件下可能的所有skuCode（去重）
        Set<String> skus = salesPlanEsLazyLoadingRepository.queryAllSku(esAggregation);
        //如果没有数据，则判定该条件查询没有结果
        if (CollectionUtils.isEmpty(skus)) {
            pageInfo.setHasNextPage(false);
            return pageInfo;
        }
        //初始化缓存信息：1.人员查询信息  2.数据信息
        MemQueryCacheDTO memQueryCacheDTO = new MemQueryCacheDTO();
        //设置状态为空闲状态：即未取完所有数据
        memQueryCacheDTO.setLoadStatus(CommonConstants.LOAD_STATUS_WAITING);
        //初始化：sku集合
        memQueryCacheDTO.setSkuSet(skus);
        memQueryCacheDTO.setAfterKey("");
        memQueryCacheDTO.setSkuSetIndex(0);
        List<EsReturnDTO> dataList = this.calculationResult(esAggregation, startIndex, endIndex, memQueryCacheDTO, new ArrayList<>());
        //将结果按照分页条件筛选出来，然后存入缓存中
        List<EsReturnDTO> esReturnDTOS = new ArrayList<>();
        if (!CollectionUtils.isEmpty(dataList)) {
            int end = dataList.size() - 1;
            if (end >= startIndex) {
                for (int i = startIndex; i <= endIndex; i++) {
                    if (i <= end) {
                        esReturnDTOS.add(dataList.get(i));
                    }
                }
            }
            redisTemplate.opsForValue().set(CommonConstants.REDIS_CACHE_PREFIX_MEM_QUERY + queryKey, memQueryCacheDTO, 30, TimeUnit.MINUTES);
            redisTemplate.opsForList().rightPushAll(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, dataList);
            redisTemplate.expire(CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, 40, TimeUnit.MINUTES);
            commonService.setCacheRecord(CommonConstants.REDIS_CACHE_PREFIX_MEM_QUERY + queryKey, CommonConstants.REDIS_CACHE_PREFIX_DATALIST + queryKey, esAggregation.getTableCode());
        }
        //如果总数据量大于页面所有数据量
        if (dataList.size() > endIndex + 1) {
            pageInfo.setHasNextPage(true);
        } else {
            pageInfo.setHasNextPage(memQueryCacheDTO.getAfterKey() != null);
        }
        //转换单位
        List<EsReturnDTO> esReturnList = salesPlanEsLazyLoadingRepository.conversionUnit(esReturnDTOS, esAggregation.getUnitType());
        pageInfo.setList(esReturnList);
        return pageInfo;
    }

    private List<EsReturnDTO> calculationResult(EsAggregationDTO esAggregation, int startIndex, int endIndex, MemQueryCacheDTO memQueryCacheDTO, List<EsReturnDTO> initList) {
        //初始已有集合元素大小
        int avaliableSize = initList.size();
        //查询数据集合
        List<EsReturnDTO> dataList = new ArrayList<>();
        //获取list集合，便于获取下标，进行计算
        List<String> skuCodeList = new ArrayList<>(memQueryCacheDTO.getSkuSet());
        //遍历获取数据
        EsScrollReturnDTO esScrollReturnDTO;
        //数据中的集合
        List<EsReturnDTO> esReturnDTOList;
        //数据中的最新afterKey
        String afterKey;
        //sku总size
        int totalSize = memQueryCacheDTO.getSkuSet().size();
        //是否跳出循环
        boolean flag = true;
        do {
            esScrollReturnDTO = salesPlanEsLazyLoadingRepository.pullDataList(skuCodeList.get(memQueryCacheDTO.getSkuSetIndex()), esAggregation, memQueryCacheDTO.getAfterKey());
            esReturnDTOList = esScrollReturnDTO.getEsReturnDTOList();
            afterKey = esScrollReturnDTO.getAfterKey();
            //加入已获取的集合
            if (CollectionUtils.isNotEmpty(esReturnDTOList)) {
                dataList.addAll(esReturnDTOList);
            }
            //如果获取的集合低于最小缓存大小或不满足页面需要的数据量  则继续获取es数据
            if (dataList.size() < MINIMUM_CACHE_SIZE || (dataList.size() + avaliableSize + startIndex - 1) < endIndex) {
                //如果afterKey为null，说明该sku数据已取完，取下一个sku的数据
                if (afterKey == null) {
                    //如果存在下一个sku
                    flag = this.checkAndSet(memQueryCacheDTO, totalSize);
                    continue;
                }
                //继续取该sku的数据
                memQueryCacheDTO.setAfterKey(afterKey);
                continue;
            }
            //如果没有任何上述问题，则可以跳出循环，设置初始化信息
            if (afterKey == null) {
                this.checkAndSet(memQueryCacheDTO, totalSize);
            } else {
                memQueryCacheDTO.setAfterKey(afterKey);
            }
            flag = false;
        } while (flag);
        return dataList;
    }

    /**
     * 判断是否含有下一个sku，如果有则设置下一个查询条件
     *
     * @param memQueryCacheDTO
     * @param totalSize
     * @return {@link boolean}
     **/
    private boolean checkAndSet(MemQueryCacheDTO memQueryCacheDTO, int totalSize) {
        if (memQueryCacheDTO.getSkuSetIndex() + 2 <= totalSize) {
            memQueryCacheDTO.setSkuSetIndex(memQueryCacheDTO.getSkuSetIndex() + 1);
            memQueryCacheDTO.setAfterKey("");
            return true;
        }
        memQueryCacheDTO.setAfterKey(null);
        memQueryCacheDTO.setLoadStatus(CommonConstants.LOAD_STATUS_STOPED);
        return false;
    }

    /**
     * 请求key生成规则：人员id+页面列表类型+查询条件
     *
     * @param esAggregation
     * @return
     */
    private String getQueryKey(EsAggregationDTO esAggregation) {
        // 根据查询条件生成请求key
        EsQueryDTO esQuery = esAggregation.getEsQuery();
        List<String> groupColumns = esAggregation.getGroupColumns();
        String userId = esAggregation.getUserId() == null ? "" : esAggregation.getUserId().toString();
        String tableCode = esAggregation.getTableCode();
        return MD5Util.MD5(esQuery.toString() + groupColumns.toString() + userId + tableCode);
    }
}
